/**
 * @fileOverview Unit related classes
 * @requires app
 */

(function() {
	/**
	 * @class Attribute class
	 * @extends app.Basic
	 * @borrows app.Basic.extend as extend
	 */
	app.Attribute = app.Basic.extend({
		/** @lends app.Attribute.prototype */
		
		/**
		 * Attribute initialization
		 *
		 * @param {Object|Number} value Initial value for attribute
		 * @param {Number} [value.base] Base value
		 * @param {Number} [value.current] Current value
		 * @param {Number} [value.max] Maximum value
		 * @param {Number} [value.min=0] Minimum value
		 */
		init : function(value) {
			if (isFinite(value)) {
				this.min = 0;
				this.base = this.current = this.max = value;
			}
			else {
				this.min = value.min || 0;
				this.current = value.current || 0;
				this.base = value.current || this.current;
				this.max = value.max || this.current;
			}
		},
		
		/**
		 * Return current value
		 *
		 * @returns {Number} Current value
		 */
		getValue : function() {
			return this.current;
		},
		
		/**
		 * Return maximum value
		 *
		 * @returns {Number} Maximum value
		 */
		getMaxValue : function() {
			return this.max;
		},
		
		/**
		 * Set a new current value for attribute
		 *
		 * @param {Number} value New current value
		 * @returns {app.Attribute} Current instance
		 */
		setValue : function(value) {
			this.current = value;
			
			if (this.current < this.min) {
				this.current = this.min;
			}
			else if (this.current > this.max) {
				this.current = this.max;
			}
			
			return this;
		},
		
		/**
		 * Modify current value with new one
		 * Modifier can be a number or a percentage value
		 *
		 * @param {Number|String} value Value modifier
		 * @returns {app.Attribute} Current instance
		 */
		modifyValue : function(value) {
			if (isFinite(value)) {
				this.setValue(this.getValue() + value);
			}
			else {
				value = parseFloat(value);
				
				this.setValue(this.getValue() / 100 * value);
			}
			
			return this;
		}
	});
	
	/* ************************************************************************************* */
	
	app.Elemental = app.Basic.extend({
		init : function(options) {
			this.elements = {
				physical : 0,
				fire : 0,
				cold : 0,
				poison : 0,
				electric : 0
			};
			
			for (var type in options) {
				this.elements[type] = new app.Attribute(options[type]);
			}
		}
	});
	
	/* ************************************************************************************* */
	
	/**
	 * @class Basic class of Unit
	 * @description Creates units;<br>each unit has its own attributes, skills, effects, etc.
	 * @extends app.Basic
	 * @borrows app.Basic.extend as extend
	 *
	 * @property {Number} level Unit level
	 * @property {String} name Unit name
	 * @property {Object} attributes List of unit attributes
	 * @property {app.SkillsCollection} skills List of unit skills
	 * @property {app.Effect[]} effects List of active effects
	 * @property {Object} exp Experience of this unit
	 * @property {Number} exp.current Current experience value
	 * @property {Number} exp.next Experience needed for level up
	 * @property {Boolean} [leader=false] Flag that unit can be a leader of some group
	 */
	app.Unit = app.Basic.extend({
		/** @lends app.Unit.prototype */
		
		/**
		 * Unit initialization
		 *
		 * @param {Object} options Unit configuration
		 */
		init : function(options) {
			// Default settings of unit
			var defaults = {
				name : 'Unit',
				level : 1,
				leader : false,
				attributes : {},
				damage : { // Basic damages
					physical : {min : 1, max : 2}
				},
				defence : { // Basic defences
					physical : 0
				},
				stats : { // Different stats that will modify basic stats
					defence : { // Damage reduction
						physical : 0
					},
					resist : {	// Damage reduction in percent
						physical : 0
					},
					mastery : {	// Damage increase in percent
						physical : 0
					},
					damageBonus : {	// Damage increase
						physical : 0
					},
					duration : {	// Increase/decrease effects duration
						poison : 0,
						cold : 0,
						fire : 0,
						curse : 0,
						buff : 0
					},
					chance : {	// Increase/decrease effect chances
						stun : 0,
						block : 0,
						curseBlock : 0
					}
				},
				skills : new app.SkillsCollection,
				effects : [],
				exp : {current : 0, next : 20}
			};
			
			$.extend(this, defaults);
			
			options || (options = {});
			
			// Apply options to this unit
			for (var key in options) {
				switch (key) {
					// Attributes
					case 'attributes':
						for (var attr in options[key]) {
							this.attributes[attr] = new app.Attribute(options[key][attr]);
						}
						
						break;
					// Skills
					case 'skills':
						for (var skill in options[key]) {
							var skillOptions = options[key][skill];
							
							if (isFinite(skillOptions)) {
								skillOptions = {level : skillOptions};
							}
							
							this.skills.add(app.SkillMgr.create(skill, skillOptions));
						}
						
						break;
					// Effects
					case 'effects':
						for (var effect in options[key]) {
							this.effects[effect] = app.EffectMgr.create(effect, options[key][effect]);
						}
						
						break;
					// Add levels
					case 'level':
						for (var i = 0; i < options[key]; i++) {
							this.addExpToNextLevel();
						}
						
						break;
					// Initialize other properties
					default:
						this[key] = options[key];
				}
			}
		},
		
		/**
		 * Get the current value of attribute
		 *
		 * @param {String} key Attribute ID
		 * @returns {Object} Current value of the attribute
		 */
		getAttr : function(key) {
			return this.attributes[key] && this.attributes[key].getValue();
		},
		
		/**
		 * Get maximum value of attribute
		 *
		 * @param {String} key Attribute ID
		 * @returns {Object} Maximum value of attribute
		 */
		getAttrMax : function(key) {
			return this.attributes[key] && this.attributes[key].getMaxValue();
		},
		
		/**
		 * Set value for the attribute
		 *
		 * @param {String} key Attribute ID
		 * @param {Number} New value for the attribute
		 */
		setAttr : function(key, value) {
			this.attributes[key].setValue(value);
			
			return this;
		},
		
		/**
		 * Set value for the attribute
		 *
		 * @param {String} key Attribute ID
		 * @param {Number|String} New value for the attribute
		 */
		modifyAttr : function(key, value) {
			this.attributes[key].modifyValue(value);
			
			return this;
		},
		
		/**
		 * Get unit's name
		 *
		 * @returns {String} Unit's name
		 */
		getName : function() {
			return this.name;
		},
		
		/**
		 * Check if unit is dead
		 *
		 * @returns {Boolean} "true" if unit is dead
		 */
		isDead : function() {
			return this.getAttr('health') <= 0;
		},
		
		/**
		 * Add some experience to this unit.<br>Will trigger {@link #event:gotexp}
		 *
		 * @param {Number} exp Amount of experience
		 * @returns {app.Unit} This instance
		 */
		addExp : function(exp) {
			this.exp.current += exp;
			
			this.trigger('gotexp', exp);
			app.debug(this + ' got ' + exp + ' experience');
			
			while (this.exp.current >= this.exp.next) {
				this.levelUp();
			}
			
			return this;
		},
		
		/**
		 * Do some actions on level up.<br> Will trigger {@link #event:levelup}
		 */
		levelUp : function() {
			this.level++;
			this.exp.current -= this.exp.next;
			this.exp.next *= 2;
			
			this.trigger('levelup', this.level);
			app.debug(this + ' got new level');
		},
		
		/**
		 * Add an amount of exp needed for next level
		 *
		 * @returns {app.Unit} Current instance
		 */
		addExpToNextLevel : function() {
			this.addExp(this.exp.next - this.exp.current);
			
			return this;
		},
		
		/**
		 * Calculate and return unit damage
		 *
		 * @returns {Object} Unit damage
		 */
		getDamage : function() {
			var damage = {};
			
			for (var type in this.damage) {
				damage[type] = {
					value : 0,
					effect : null
				};
				
				damage[type].value = isFinite(this.damage[type]) ? this.damage[type] : app.Util.random(this.damage[type].min, this.damage[type].max);
				damage[type].value += damage[type] / 100 * parseFloat(this.stats.mastery[type]);
				damage[type].value += isFinite(this.stats.damageBonus[type]) ? this.stats.damageBonus[type] : app.Util.random(this.stats.damageBonus[type].min, this.stats.damageBonus[type].max);
				
				if (this.damage[type].duration > 0) {
					damage[type].effect = new app.Effect({
						
					});
				}
				
				damage[type].duration = this.damage[type].duration;
				damage[type].duration += this.stats.duration[type];
			}
		},
		
		/**
		 * Get unit action
		 */
		getAction : function() {
			return this.skills.getRandom();
		},
		
		/**
		 * Returns string representation of unit
		 *
		 * @returns {String} String representation of unit
		 */
		toString : function() {
			return '[' + this.level + '] ' + this.name+ ' (' + this.getAttr('health') + '/' + this.getAttrMax('health') + ')';
		},
		
		/**
		 * Add an effect to the list of active effects
		 *
		 * @param {app.Effect} effect Effect to apply
		 * @returns {app.Unit} Current instance
		 */
		addEffect : function(effect) {
			this.effects.push(effect);
			
			return this;
		}
	});
	
	/**
	 * @name app.Unit#gotexp
	 * @event
	 * @param {Number} exp Amount of experience gained
	 */
	
	/**
	 * @name app.Unit#levelup
	 * @event
	 * @param {Number} level Gained level
	 */
	
	/* ************************************************************************************* */
	
	/**
	 * @class Group of units
	 * Contains instances of class app.Unit
	 *
	 * @extends app.Collection
	 * @borrows app.Basic.extend as extend
	 *
	 * @property {String} [name] Group name
	 */
	app.UnitGroup = app.Collection.extend({
		/** @lends app.UnitGroup.prototype */
		
		/**
		 * Unit group initialization
		 *
		 * @param {Object|Array} [options] Initialization options
		 * @param {String} [options.name] Group name
		 * @param {Array} [options.items] Initial items
		 */
		init : function(options) {
			if (!options) {
				return;
			}
			
			if (app.Util.isArray(options)) {
				this.callParent('init', options);
			}
			else {
				this.name = options.name;
				this.callParent('init', options.items);
			}
		},
		
		/**
		 * Check if the entire group is dead
		 *
		 * @returns {Boolean} <b>true</b> if the entire group is dead
		 */
		isDead : function() {
			for (var i = 0; i < this.size(); i++) {
				if (!this.get(i).isDead()) {
					return false;
				}
			}
			
			return true;
		},
		
		/**
		 * Get all units that are ready to do some actions; units should be alive
		 *
		 * @param {Number} tick Tick of current timer in milliseconds
		 * @returns {app.UnitGroup} Group of ready units
		 */
		getReady : function(tick) {
			return this.filter(function(unit) {
				return !unit.isDead() && unit.getAttr('speed') % tick === 0;
			});
		},
		
		/**
		 * Get all alive units
		 *
		 * @returns {app.UnitGroup} Group of alive units
		 */
		getAlive : function() {
			return this.filter(function(unit) {
				return !unit.isDead();
			});
		},
		
		/**
		 * Get the leader of this group
		 *
		 * @returns {app.Unit} Leader unit
		 */
		getLeader : function() {
			var leaderUnit = this.filter(function(unit) {
				return unit.leader;
			}).get(0);
			
			if (!leaderUnit) {
				leaderUnit = this.get(0);
			}
			
			return leaderUnit;
		},
		
		/**
		 * Get the name of this group
		 *
		 * @returns {String} Name of this group
		 */
		getName : function() {
			return this.name || this.getLeader().getName();
		}
	});
	
	/* ************************************************************************************* */
	
	/**
	 * @class Units manager
	 * @extends app.Manager
	 */
	app.UnitMgr = new app.Manager(app.Unit);
})();